#include <bx/timer.h>
#include <bx/math.h>
#include "entry/entry.h"
#include "entry/cmd.h"
#include "entry/input.h"
#include <bx/allocator.h>
#include "camera/myCamera.h"
#include <iostream>

int myCmdMove(CmdContext* /*_context*/, void* /*_userData*/, int _argc, char const* const* _argv)
 {
 	if (_argc > 1)
 	{
 		if (0 == bx::strCmp(_argv[1], "forward"))
 		{
 			myCameraSetKeyState(CAMERA_KEY_FORWARD, true);
 			return 0;
 		}
 		else if (0 == bx::strCmp(_argv[1], "left"))
 		{
			myCameraSetKeyState(CAMERA_KEY_LEFT, true);
 			return 0;
 		}
 		else if (0 == bx::strCmp(_argv[1], "right"))
 		{
			myCameraSetKeyState(CAMERA_KEY_RIGHT, true);
 			return 0;
 		}
 		else if (0 == bx::strCmp(_argv[1], "backward"))
 		{
			myCameraSetKeyState(CAMERA_KEY_BACKWARD, true);
 			return 0;
 		}
 	}
 
 	return 1;
 }

static void myCmd(const void* _userData)
{
	cmdExec((const char*)_userData);
}

static const InputBinding s_myCamBindings[] =
{
	{ entry::Key::KeyW,             entry::Modifier::None, 0, myCmd, "move forward"  },
	{ entry::Key::KeyA,             entry::Modifier::None, 0, myCmd, "move left"     },
	{ entry::Key::KeyS,             entry::Modifier::None, 0, myCmd, "move backward" },
	{ entry::Key::KeyD,             entry::Modifier::None, 0, myCmd, "move right"    },
	INPUT_BINDING_END
};

struct MyCamera
{
	struct MouseCoords {
		int m_mx;
		int m_my;
		int m_mz;
	};

	MyCamera() {
		reset();
		entry::MouseState mouseState;
		update(0.0f, mouseState, true);

		cmdAdd("move", myCmdMove);
		inputAddBindings("camBindings", s_myCamBindings);
	}

	void setKeyState(uint8_t _key, bool _down)
	{
		m_keys &= ~_key; 		// set the bit of the key to 0
		m_keys |= _down ? _key : 0;
	}

	void update(float _deltaTime, const entry::MouseState& _mouseState, bool _reset) {
		if (_reset)
		{
			m_mouseLast.m_mx = _mouseState.m_mx;
			m_mouseLast.m_my = _mouseState.m_my;
			m_mouseLast.m_mz = _mouseState.m_mz;
			m_mouseNow = m_mouseLast;
			m_mouseDown = false;

			return;
		}
		if (!m_mouseDown)
		{
			m_mouseLast.m_mx = _mouseState.m_mx;
			m_mouseLast.m_my = _mouseState.m_my;
		}

		m_mouseDown = !!_mouseState.m_buttons[entry::MouseButton::Left];

		if (m_mouseDown)
		{
			m_mouseNow.m_mx = _mouseState.m_mx;
			m_mouseNow.m_my = _mouseState.m_my;
			m_horizontalAngle += m_mouseSpeed * float(m_mouseNow.m_mx - m_mouseLast.m_mx);
			m_verticalAngle -= m_mouseSpeed * float(m_mouseNow.m_my - m_mouseLast.m_my);
			m_mouseLast.m_mx = m_mouseNow.m_mx;
			m_mouseLast.m_my = m_mouseNow.m_my;
		}

		m_mouseLast.m_mz = m_mouseNow.m_mz;
		m_mouseNow.m_mz = _mouseState.m_mz;

		// roller down : delta < 0 ;  roller up : delta > 0
		const float deltaZ = float(m_mouseNow.m_mz - m_mouseLast.m_mz);
		m_fov = m_fov - deltaZ * 2.0f;

		const bx::Vec3 direction =
		{
			bx::cos(m_verticalAngle) * bx::sin(m_horizontalAngle),
			bx::sin(m_verticalAngle),
			bx::cos(m_verticalAngle) * bx::cos(m_horizontalAngle),
		};
		const bx::Vec3 right = bx::cross(direction, m_worldUp);
		const bx::Vec3 up = bx::cross(right, direction);

		if (m_keys & CAMERA_KEY_FORWARD)
		{
			m_eye = bx::mad(direction, _deltaTime * m_moveSpeed, m_eye);
			setKeyState(CAMERA_KEY_FORWARD, false);
		}

		if (m_keys & CAMERA_KEY_BACKWARD)
		{
			m_eye = bx::mad(direction, -_deltaTime * m_moveSpeed, m_eye);
			setKeyState(CAMERA_KEY_BACKWARD, false);
		}

		if (m_keys & CAMERA_KEY_LEFT)
		{
			m_eye = bx::mad(right, _deltaTime * m_moveSpeed, m_eye);
			setKeyState(CAMERA_KEY_LEFT, false);
		}

		if (m_keys & CAMERA_KEY_RIGHT)
		{
			m_eye = bx::mad(right, -_deltaTime * m_moveSpeed, m_eye);
			setKeyState(CAMERA_KEY_RIGHT, false);
		}

		m_at = bx::add(m_eye, direction);
		m_up = up;
	}

	void reset() {
		m_mouseNow.m_mx = 0;
		m_mouseNow.m_my = 0;
		m_mouseLast.m_mx = 0;
		m_mouseLast.m_my = 0;
		m_mouseLast.m_mz = 0;
		// camera position
		m_eye.x = 0.0f;
		m_eye.y = 0.0f;
		m_eye.z = -35.0f;
		// towards the origin
		m_at.x = 0.0f;
		m_at.y = 0.0f;
		m_at.z = 0.0f;
		// up direction
		m_up.x = 0.0f;
		m_up.y = 1.0f;
		m_up.z = 0.0f;
		m_worldUp.x = 0.0f; 
		m_worldUp.y = 1.0f;
		m_worldUp.z = 0.0f;

		m_horizontalAngle = 0.0f;
		m_verticalAngle = 0.0f;
		m_mouseSpeed = 0.0020f;
		m_gamepadSpeed = 0.04f;
		m_moveSpeed = 30.0f;
		m_keys = 0;
		m_mouseDown = false;

		m_fov = 60.0f;
	
	}

	void getViewMtx(float* _viewMtx)
	{
		bx::mtxLookAt(_viewMtx, bx::load<bx::Vec3>(&m_eye.x), bx::load<bx::Vec3>(&m_at.x), bx::load<bx::Vec3>(&m_up.x));
	}

	void setPosition(const bx::Vec3& _pos)
	{
		m_eye = _pos;
	}

	void setVerticalAngle(float _verticalAngle)
	{
		m_verticalAngle = _verticalAngle;
	}

	void setHorizontalAngle(float _horizontalAngle)
	{
		m_horizontalAngle = _horizontalAngle;
	}

	void setFOV(float _fov) {
		m_fov = _fov;
	}

	MouseCoords m_mouseNow;
	MouseCoords m_mouseLast;

	bx::Vec3 m_eye = bx::init::Zero;
	bx::Vec3 m_at = bx::init::Zero;
	bx::Vec3 m_up = bx::init::Zero;
	bx::Vec3 m_worldUp = bx::init::Zero;
	float m_horizontalAngle;
	float m_verticalAngle;

	float m_mouseSpeed;
	float m_gamepadSpeed;
	float m_moveSpeed;

	float m_fov;

	uint8_t m_keys;
	bool m_mouseDown;
};

static MyCamera* s_camera = NULL;

void myCameraCreate()
{
	s_camera = BX_NEW(entry::getAllocator(), MyCamera);
}

void myCameraReset() {
	s_camera->reset();
}

void myCameraDestroy()
{
	BX_DELETE(entry::getAllocator(), s_camera);
	s_camera = NULL;
}

void myCameraSetPosition(const bx::Vec3& _pos)
{
	s_camera->setPosition(_pos);
}

void myCameraSetHorizontalAngle(float _horizontalAngle)
{
	s_camera->setHorizontalAngle(_horizontalAngle);
}

void myCameraSetVerticalAngle(float _verticalAngle)
{
	s_camera->setVerticalAngle(_verticalAngle);
}

void myCameraSetKeyState(uint8_t _key, bool _down)
{
	s_camera->setKeyState(_key, _down);
}

void myCameraGetViewMtx(float* _viewMtx)
{
	s_camera->getViewMtx(_viewMtx);
}

bx::Vec3 myCameraGetPosition()
{
	return s_camera->m_eye;
}

bx::Vec3 myCameraGetAt()
{
	return s_camera->m_at;
}

void myCameraUpdate(float _deltaTime, const entry::MouseState& _mouseState, bool _reset)
{
	s_camera->update(_deltaTime, _mouseState, _reset);
}

float myCameraFOV() {
	return s_camera->m_fov;
}

